<?php

use App\Library\DBRSAUtil;
use App\Library\Logic\EnvLogic;
use App\Library\MySeasLog;
use App\Library\RabbitMQPub;
use App\Library\Requests\Base\BaseDTORequest;
use App\Library\Requests\Base\BasePageRequest;
use App\Library\Rules\LikeRule;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Facades\DB;
use GuzzleHttp\Client;
use App\Library\Lock;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Redis;

/**
 * fileIdToUrl
 * @param string $fileId
 * @return string
 */
function fileIdToUrl(string $fileId): string
{
    if (preg_match(__('com.serialNumberReg'), $fileId)) return config('oss.url') . fileIdToDir($fileId);
    else return config('oss.url') . $fileId;
}

function fileIdToDir(string $fileId): string
{
    return implode('/', [
        substr($fileId, 0, 4),
        substr($fileId, 4, 2),
        substr($fileId, 6, 2),
        substr($fileId, 8, 2),
        substr($fileId, 10),
    ]);
}

/**
 * @throws Exception
 */
function couponExpire(): void
{
    RabbitMQPub::send(['command' => 'couponExpire']);
}

/**
 *
 * @param string $oddNumbers
 * @return mixed
 * @throws GuzzleException
 *
 */
function identifyOddNumbers(string $oddNumbers): mixed
{
    $url = sprintf(__('logistics.url'), $oddNumbers, __('logistics.key'));
    $client = new Client();
    $result = json_decode($client->get($url)->getBody(), true);
    return empty($result) ? [] : $result[0];
}

/**
 * fail
 * @param int $code
 * @return JsonResponse
 */
function fail(int $code = -1): JsonResponse
{
    $arr = [
        'code' => $code,
        'message' => __('errorCode.' . $code),
        'timestamp' => now()->format('Y-m-d H:i:s.u'),
    ];
    MySeasLog::warning($arr);
    return response()->json($arr);
}

/**
 * success
 * @param mixed $data
 * @param string $msg
 * @return JsonResponse
 */
function success(mixed $data = [], string $msg = 'success'): JsonResponse
{
    $jsonData = [
        'code' => 200,
        'msg' => $msg,
        'data' => $data,
        'timestamp' => now()->format('Y-m-d H:i:s.u'),
    ];
    MySeasLog::notice($jsonData);
    return response()->json($jsonData);
}

/**
 * failCodeMessage
 * @param int $code
 * @param string $message
 * @return JsonResponse
 */
function failCodeMessage(int $code = -1, string $message = ''): JsonResponse
{
    $jsonData = [
        "code" => $code,
        "message" => $message,
        'timestamp' => now()->format('Y-m-d H:i:s.u'),
    ];
    MySeasLog::warning($jsonData);
    return response()->json($jsonData);
}

/**
 * failMessage
 * @param string $message
 * @return JsonResponse
 */
function failMessage(string $message = ''): JsonResponse
{
    return failCodeMessage(-1, $message);
}

/**
 * mq
 * @param string $name
 * @param Model|array|string|null $object
 * @param Carbon|int|string|null $delay
 * @return void
 * @throws Exception
 */
function mq(string $name, Model|array|string|null $object, Carbon|int|string|null $delay = 0): void
{
    if (is_null($object)) return;
    else if ($object instanceof Model) $object = $object->serial_number;
    else if (is_array($object)) $object = $object['serialNumber'];
    if ($delay instanceof Carbon) {
        if (now()->gte($delay)) $delay = 0;
        else $delay = $delay->diffInSeconds(now());
    } else if (is_string($delay)) {
        if (now()->gte(now()->parse($delay))) $delay = 0;
        else $delay = now()->parse($delay)->diffInSeconds(now());
    }
    RabbitMQPub::send(['command' => $name, 'delay' => $delay, 'param' => ['serialNumber' => $object]]);
}

/**
 *
 * @param Model|array|string|null $object
 * @param Carbon|int|string|null $delay
 * @return void
 * @throws Exception
 *
 */
function pushSmsMessage(Model|array|string|null $object, Carbon|int|string|null $delay = 0): void
{
    RabbitMQPub::send(['command' => 'auctionPushSmsMessage', 'delay' => $delay, 'param' => ['content' => $object]]);
}

/**
 * random
 * @param int $number
 * @param int $num
 * @param string $str
 * @return string
 */
function random(int $number = 6, int $num = 0, string $str = ''): string
{
    $string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    for ($i = 0; $i < $number; $i++) {
        $str .= $string[mt_rand(0, $num ? 9 : strlen($string) - 1)];
    }
    return $str;
}

/**
 * 将驼峰式命名转换为下划线式命名
 * @param string|array $obj
 * @param array $arr
 * @return string|array
 */
function humpToLine(string|array $obj, array $arr = []): string|array
{
    if (is_string($obj)) return strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($obj)));
    else if (array_key_first($obj) === 0) {
        foreach ($obj as $k => $v) {
            if ($v instanceof Expression) continue;
            else if (is_array($v)) $obj[$k] = humpToLine($v);
            else $obj[$k] = strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($v)));
        }
        return $obj;
    } else {
        foreach ($obj as $k => $v) $arr[strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($k)))] = $v;
        return $arr;
    }
}

/**
 * 将下划线式命名转换为驼峰式命名
 * @param string|array $obj
 * @param array $arr
 * @return array|string
 */
function lineToHump(string|array $obj, array $arr = []): array|string
{
    if (is_string($obj)) return lcfirst(str_replace('_', '', ucwords($obj, '_')));
    else if (array_key_first($obj) === 0) {
        foreach ($obj as $v) $arr[] = lcfirst(str_replace('_', '', ucwords($v, '_')));
    } else {
        foreach ($obj as $k => $v) $arr[lcfirst(str_replace('_', '', ucwords($k, '_')))] = $v;
    }
    return $arr;
}

/**
 * serialNumber
 * @param int $number
 * @return string
 */
function serialNumber(int $number = 20): string
{
    if ($number === 20) return now()->format('YmdHisu');
    else if ($number > 20) return now()->format('YmdHisu') . random($number - 20, 1);
    else return random($number, 1);
}


/**
 * prod
 * @return bool
 */
function prod(): bool
{
    if (config('app.env') === 'production') return true;
    else if (str_contains(config('app.url'), '127.0.0.1')) return false;
    else if (str_contains(config('app.url'), 'test.')) return false;
    else if (config('app.env') === 'local') return false;
    return true;
}

/**
 * getCacheUploadFile
 * @param string $serialNumber
 * @return ?string
 */
function getCacheUploadFile(string $serialNumber): ?string
{
    return DB::table('upload_File')->where('serial_number', $serialNumber)->value('mime_type');
}

/**
 * handleRequest
 * @param BaseDTORequest $request
 * @param array|null $needle
 * @return array
 */
function handleRequest(BaseDTORequest $request, array $needle = null): array
{
    $return = [];
    foreach ($request->rules() as $k => $v) {
        if (is_null($request->$k)) continue;
        else if (!is_null($needle) && !in_array($k, $needle)) continue;
        else $return[$k] = $request->$k;
    }
    return $return;
}

/**
 * helpBuilder
 * @param Builder $builder
 * @param BaseDTORequest $request
 * @return Builder
 */
function helpBuilder(Builder $builder, BaseDTORequest $request): Builder
{
    $columns = handleRequest($request);
    foreach ($columns as $key => $value) {
        if (in_array($key, ['limit', 'page', 'ascDesc', 'isLogoff'])) continue;
        else if (is_null($value) || is_null($request->$key)) continue;
        else if ($key === 'sortColumn') {
            $value = str_contains($value, '.') ? $value : $builder->from . '.' . $value;
            $builder->orderBy(humpToLine($value), $request->ascDesc ?? 'asc');
        } else if ($key === 'deletedAt') {
            $field = humpToLine($builder->from . '.' . $key);
            $value ? $builder->whereNotNull($field) : $builder->whereNull($field);
        } else if ($key === 'startAt' && strlen($value) === 19) {
            $field = humpToLine($builder->from . '.' . 'createdAt');
            $builder->where($field, '>=', $value);
        } else if ($key === 'endAt' && strlen($value) === 19) {
            $field = humpToLine($builder->from . '.' . 'createdAt');
            $builder->where($field, '<=', $value);
        } else {
            if (is_array($request->rules()[$key])) {
                foreach ($request->rules()[$key] as $v) {
                    if ($v instanceof LikeRule) {
                        if (str_contains($key, '.')) $field = humpToLine($key);
                        else $field = humpToLine($builder->from . '.' . $key);
                        $builder->where($field, 'like', "%$value%");
                        break;
                    }
                }
            } else if (count(explode('.', $key)) === 2) {
                $builder->where(humpToLine($key), $value);
            } else {
                if (str_contains($key, '.')) $field = humpToLine($key);
                else $field = humpToLine($builder->from . '.' . $key);
                $builder->where($field, $value);
            }
        }
    }
    return $builder;
}

/**
 * payLog
 * @param string|array|null $string |array $string $string
 * @return void
 */
function payLog(string|array|null $string = null): void
{
    if (is_null($string)) $string = random();
    else if (is_array($string)) $string = print_r($string, true);
    Storage::append('pay/' . now()->toDateString() . '.log', now()->toDateTimeString('microsecond') . ' ' . $string);
}

/**
 * paginate
 * @param LengthAwarePaginator|BasePageRequest $paginate
 * @return array
 */
function paginate(LengthAwarePaginator|BasePageRequest $paginate): array
{
    if ($paginate instanceof BasePageRequest) {
        $array = [
            'limit' => is_null($paginate->limit) ? 15 : $paginate->limit,
            'page' => is_null($paginate->page) ? 1 : $paginate->page,
            'total' => 0,
            'totalPage' => 0,
            'data' => [],
        ];
    } else {
        $array = [
            'limit' => $paginate->perPage(),
            'page' => $paginate->currentPage(),
            'total' => $paginate->total(),
            'totalPage' => $paginate->lastPage(),
            'data' => $paginate->items(),
        ];
    }
    unset($paginate);
    return $array;
}

/**
 * redis
 * @return mixed
 */
function redis(): mixed
{
    $args = func_get_args();
    $method = $args[0];
    unset($args[0]);
    return Redis::command($method, $args);
}

/**
 * aesDecrypt
 * @param string $string
 * @return array|int
 */
function aesEncrypt(string $string): false|string
{
    $iv = __('base.iv');
    return openssl_encrypt($string, 'aes-128-cbc', strrev($iv), 0, $iv);
}

/**
 * aesDecrypt
 * @param string $string
 * @return array|int
 */
function aesDecrypt(string $string): false|string
{
    $iv = __('base.iv');
    return openssl_decrypt($string, 'aes-128-cbc', strrev($iv), 0, $iv);
}

/**
 * unsetObject
 * @param ?object $object $object
 * @param array|string $columns
 * @return void
 */
function unsetObject(?object $object, array|string $columns): void
{
    if (is_array($columns)) {
        foreach ($columns as $v) {
            unset($object->$v);
        }
    } else if (is_string($columns)) {
        unset($object->$columns);
    }
}

/**
 * delZero
 * @param string $code
 * @return string|null
 * @throws GuzzleException
 */
function getOpenIdByCode(string $code): ?string
{
    $key = config('app.wechat.mini_app_id');
    $secret = config('app.wechat.mini_app_secret');
    $client = new Client;
    $url = 'https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code';
    $response = $client->get(sprintf($url, $key, $secret, $code));
    return json_decode($response->getBody()->getContents());
}

/**
 * delZero
 * @param string|null $str
 * @return string|null
 */
function delZero(string $str = null): ?string
{
    if (is_null($str)) return null;
    return str_contains($str, '.') ? rtrim(rtrim($str, '0'), '.') : $str;
}

/**
 * hCollection
 * @param Collection $collection
 * @param string $primaryKey
 * @return array
 */
function hCollection(Collection $collection, string $primaryKey = 'id'): array
{
    $arr = [];
    foreach ($collection as $v) {
        $primaryValue = $v->$primaryKey;
        unset($v->$primaryKey);
        $arr[$primaryValue] = $v;
    }
    unset($collection);
    return $arr;
}

/**
 * migrationUp
 * @param string $fileString
 * @return void
 */
function migrationUp(string $fileString): void
{
    ini_set('memory_limit', '-1');
    $start = strpos($fileString, '_create_');
    $str = substr($fileString, $start + 8);
    $str = substr($str, 0, -10);
    DB::statement('set names utf8mb4');
    DB::statement('set foreign_key_checks=0');
    $dir = database_path('migrations/sql/');
    $sqlStr = file_get_contents($dir . $str . '.sql');
    if (strlen($sqlStr)) DB::unprepared($sqlStr);
    DB::statement('set foreign_key_checks=1');
}

function priceSection(Collection|array|null $collection): float|false|int|string|null
{
    if (is_null($collection)) return null;
    else if (is_array($collection)) {
        $arr = array_map(function ($spec) {
            return $spec['ps_price_underlined'] * 1;
        }, $collection);
    } else {
        $arr = $collection->map(function ($spec) {
            return (float)$spec->ps_price_underlined;
        });
        $arr = array_unique($arr->toArray());
    }
    sort($arr);
    if (empty($arr)) return null;
    else if (count($arr) === 1) return end($arr);
    else return $arr[0] . '~' . end($arr);
}

/**
 * encodeId
 * @param int $id
 * @return int
 */
function encodeId(int $id): int
{
    $sid = ($id & 0xff000000);
    $sid += ($id & 0x0000ff00) << 8;
    $sid += ($id & 0x00ff0000) >> 8;
    $sid += ($id & 0x0000000f) << 4;
    $sid += ($id & 0x000000f0) >> 4;
    $sid ^= 2147483647;
    return $sid;
}

/**
 * decodeId
 * @param int $sid
 * @return int
 */
function decodeId(int $sid): int
{
    $sid ^= 2147483647;
    $id = ($sid & 0xff000000);
    $id += ($sid & 0x00ff0000) >> 8;
    $id += ($sid & 0x0000ff00) << 8;
    $id += ($sid & 0x000000f0) >> 4;
    $id += ($sid & 0x0000000f) << 4;
    return $id;
}

/**
 * categoryHelp
 * @param int $id
 * @param array $arr
 * @param object $obj
 * @return string|null
 */
function categoryHelp(int $id, array $arr, object $obj): ?string
{
    if ($id === 0) return null;
    else return implode('-', [$arr[$id], $arr[$obj->category_id_two], $arr[$obj->category_id_three]]);
}

/**
 * es
 * @return string
 */
function es(): string
{
    $host = request()->host();
    if (str_starts_with($host, 'localhost')) return 'shoptest';
    else if (str_starts_with($host, '127.0.')) return 'shoptest';
    else if (str_starts_with($host, '192.168.')) return 'shoptest';
    else if (str_contains($host, '.shop.')) return 'shop';
    else return 'shoptest';
}

/**
 * esCache
 * @param string|null $es
 * @return array
 */
function esCache(string $es = null): array
{
    $k = 'env';
    $es = $es ?? es();
    $info = cache($k . ':' . $es);
    if (!is_null($info)) return $info;
    return Lock::get('envLock', function () use ($es) {
        $info = EnvLogic::findByWhere('key', $es, 'info');
        if (is_null($info)) {
            $info = __('wechat_setting.' . $es);
            EnvLogic::insertForModel(['key' => $es, 'info' => $info]);
        }
        cache(['env:' . $es => $info]);
        return $info;
    });
}

function forgetCache(int|null $id = null): void
{
    $id = $id ?? USERID;
    if ($id === config('app.superId')) return;
    $key = 'ztoken:' . $id;
    $array = redis('zrange', $key, 0, -1);
    foreach ($array as $v) cache()->forget(__('base.tokenPrefix') . $v);
    redis('del', $key);
}

/**
 * userId
 * @return int
 */
function userId(): int
{
    $token = request()->header(str_replace(':', '', __('base.tokenPrefix')));
    if (!empty($token) && strlen($token) === 32) {
        $k = __('base.tokenPrefix') . $token;
        if (!cache()->has($k)) return 0;
        else return (int)cache($k);
    } else {
        $auth = request()->header('Authorization');
        if (empty($auth)) {
            $newAuth = request()->header('newAuth');
            if (empty($newAuth)) {
                $tp = __('base.tokenPrefix');
                $token = request()->header(str_replace(':', '', $tp));
                if (!is_string($token) || strlen($token) != 32) return 0;
                else if (!cache()->has($tp . $token)) return 0;
                return (int)cache($tp . $token);
            }
            return decodeId(aesDecrypt($newAuth));
        } else if (strlen($auth) === 24) return decodeId(aesDecrypt($auth));
        else {
            if (strlen($auth) === 32) {
                $tp = __('base.tokenPrefix');
                if (!cache()->has($tp . $auth)) return 0;
                else return (int)cache($tp . $auth);
            } else {
                $json = DBRSAUtil::rsaDecrypt($auth);
                if (empty($json)) return 0;
                return json_decode($json, true)['user_id'] ?? 0;
            }
        }
    }
}

/**
 * signature
 * @return int
 */
function signature(): int
{
    return encodeId(mt_rand(1000000000, 2000000000));
}

/**
 * cacheUser
 * @param Model $shopUser
 * @param int $db
 * @return array
 */
function cacheUser(Model $shopUser, int $db = 0): array
{
    $array = [
        'id' => $shopUser->user_id,
        'authStatus' => $shopUser->dm->auth_status ?? 0,
        'createdAt' => $shopUser->add_at,
        'inviteCode' => $shopUser->invite_code,
        'system' => $shopUser->is_system,
        'realName' => $shopUser->dm->real_name ?? null,
        'avatar' => $shopUser->user_avatar,
        'province' => $shopUser->user_province,
        'city' => $shopUser->user_city,
        'county' => $shopUser->user_county,
        'mainId' => $shopUser->user_main_id ?? 0,
        'mobile' => $shopUser->user_mobile,
        'nickname' => $shopUser->user_nickname,
        'signature' => $shopUser->user_note,
        'status' => $shopUser->user_status,
        'type' => $shopUser->user_type,
        'sex' => $shopUser->user_sex,
        'userName' => $shopUser->user_name,
        'delete' => $shopUser->user_delete_flag === 1,
        'power' => $shopUser->user_peer_power === 1,
        'agent' => $shopUser->agent,
    ];
    if ($db === 0) cache(['user:' . $shopUser->user_id => $array]);
    return $array;
}

/**
 * 获取公众号及小程序access_token
 * @param string $type ;mini:小程序;mp:公众号
 * @return string|null
 * @throws GuzzleException
 */
function wechatAccessToken(string $type): ?string
{
    $client = new Client;
    $response = json_decode($client->post(__('wechat.stableToken'), [
        'json' => [
            'grant_type' => 'client_credential',
            'appid' => __('wechat.' . $type . '.appId'),
            'secret' => __('wechat.' . $type . '.secret'),
        ],
    ])->getBody()->getContents());
    return $response->access_token ?? null;
}

/**
 * 微信模板消息推送
 * @param string $type
 * @param string $param
 * @return void
 * @throws Exception
 */
function wechatTemplate(string $type, string $param): void
{
    RabbitMQPub::send(['command' => 'templateMessage', 'param' => ['type' => $type, 'serialNumber' => $param]]);
}

function loginByCode(string $code, string $type): JsonResponse|array
{
    $lWechat = __('wechat');
    $newType = $type === 'mini' ? 'miniapp' : $type;
    $key = Arr::get($lWechat, implode('.', [$newType, 'appId']));
    $secret = Arr::get($lWechat, implode('.', [$newType, 'secret']));

    if ($type === 'mp') $url = sprintf(Arr::get($lWechat, implode('.', [$type, 'url', 'step2'])), $key, $secret, $code);
    else $url = sprintf(Arr::get($lWechat, implode('.', [$type, 'url'])), $key, $secret, $code);
    $client = new Client;
    $data = json_decode($client->get($url)->getBody()->getContents(), true);
    if (!empty($data['errcode'])) return failMessage($data['errmsg']);
    else if (empty($data['unionid'])) return fail(499);
    return $data;
}

